﻿//=============================================================================
// Библиотека 'WPFExcelReport' предназначена для связи и передачи данных c PLC
//=============================================================================

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Net;
using System.Diagnostics;
using System.Threading;

namespace WPFExcelReport.S7
{
    /// <summary>
    /// Передача данных
    /// </summary>
    class TransferData
    {
        #region Константы

        /// <summary>
        /// Макс размер посылки, байт
        /// </summary>
        private const int PAKET_LENGTH = 460;

        #endregion

        #region Поля

        #region Определенные сообщения

        /// <summary>
        /// Отпр сообщение для установки соединения
        /// </summary>
        byte[] _bSend1OpenConn, _bSend2OpenConn, _bSend3OpenConn;

        /// <summary>
        /// Получ сообщение для установки соединения
        /// </summary>
        byte[] _bRecOpenConn;

        /// <summary>
        /// Отпр сообщение для получения данных
        /// </summary>
        byte[] _bSendForRead;

        /// <summary>
        /// Получ сообщение - полученные данные
        /// </summary>
        byte[] _bRecResultRead;

        /// <summary>
        /// Отпр сообщение для отправки данных
        /// </summary>
        byte[] _bSendForWrite;

        /// <summary>
        /// Получ сообщение после отправки данных
        /// </summary>
        byte[] _bRecAfterWrite;

        #endregion

        #region Объекты для связи

        /// <summary>
        /// Клиент tcp
        /// </summary>
        private TcpClient _tcpClient;

        /// <summary>
        /// Поток данных
        /// </summary>
        private NetworkStream _netStream;

        /// <summary>
        /// Локал точка соединения
        /// </summary>
        private IPEndPoint _localPnt;

        /// <summary>
        /// Удален точка соединения
        /// </summary>
        private IPEndPoint _remotePnt;

        #endregion

        #region Общие структуры

        /// <summary>
        /// Список используемых DB для чтения
        /// </summary>
        private List<DB> _DBRead;

        /// <summary>
        /// Список используемых DB для записи
        /// </summary>
        private List<DB> _DBWrite;

        /// <summary>
        /// Область M для чтения
        /// </summary>
        private M _mRead = new M();

        /// <summary>
        /// Область M для записи
        /// </summary>
        private M _mWrite = new M();

        /// <summary>
        /// Значения переменных для чтения: ключ - строка адреса переменной
        /// </summary>
        private Dictionary<string, ItemValue> _iReadRes;

        /// <summary>
        /// Значения переменных для записи: ключ - строка адреса переменной
        /// </summary>
        private Dictionary<string, ItemValue> _iWriteRes;

        /// <summary>
        /// Адреса переменных для чтения: ключ - строка адреса переменной
        /// </summary>
        private Dictionary<string, Addr> _iReadAddr;

        /// <summary>
        /// Адреса переменных для записи: ключ - строка адреса переменной
        /// </summary>
        private Dictionary<string, Addr> _iWriteAddr;

        #endregion

        /// <summary>
        /// PLC
        /// </summary>
        private PlcDesc _plc;
      
        /// <summary>
        /// Соединение открыто
        /// </summary>
        private bool _isConnOpen = false;
       

        #endregion

        #region Свойства

        /// <summary>
        /// Изменившиеся данные
        /// </summary>
        public List<ItemValue> ItemsChange = new List<ItemValue>(100);

        /// <summary>
        /// Ошибка
        /// </summary>
        public ErrCode Error;

        /// <summary>
        /// Связь
        /// </summary>
        public bool IsConnect
        {
            get {return _isConnOpen; }
        }


        #endregion

        #region Конструктор объекта

        /// <summary>
        /// Конструктор объекта TransferData
        /// </summary>
        internal TransferData(List<DB> DBRead,
                              List<DB> DBWrite, 
                              M mRead,
                              M mWrite,
                              Dictionary<string, ItemValue> iReadRes,
                              Dictionary<string, ItemValue> iWriteRes,
                              Dictionary<string, Addr> iReadAddr,
                              Dictionary<string, Addr> iWriteAddr)
        {
            this._DBRead = DBRead;
            this._DBWrite = DBWrite;
            this._mRead = mRead;
            this._mWrite = mWrite;
            this._iReadRes = iReadRes;
            this._iWriteRes = iWriteRes;
            this._iReadAddr = iReadAddr;
            this._iWriteAddr = iWriteAddr;

        }

        #endregion
             
        #region Внутренние методы
                               
        /// <summary>
        /// Получение значения элемента из массива
        /// </summary>
        /// <param name="addr"> адрес переменной</param>
        /// <param name="bArr"> массив байт</param>
        /// <returns> объект результата</returns>
        private object TakeFromArray(byte[] bArr, Addr addr)
        {
            switch (addr.ValType)
            {
                case ValType.ByteT:
                    return bArr[bArr.Count() - addr.StartByte - 1];

                case ValType.IntT:
                    return BitConverter.ToInt16(bArr, bArr.Count() - addr.StartByte - 2);

                case ValType.DIntT:
                    return BitConverter.ToInt32(bArr, bArr.Count() - addr.StartByte - 4);

                case ValType.RealT:
                    return BitConverter.ToSingle(bArr, bArr.Count() - addr.StartByte - 4);

                case ValType.StringT:
                    int cnt = bArr.Count() - addr.StartByte - addr.Length;
                    IEnumerable<byte> ie = bArr.Where((e, i) => (cnt <= i) && (i < cnt + addr.Length))
                                               .Reverse();
                    return Encoding.GetEncoding(1251).GetString(ie.ToArray<byte>());

                default:
                    return 0;
            }
        }

        /// <summary>
        /// Получение массива байт из значения элемента
        /// </summary>
        /// <param name="val"> значение переменной</param>
        /// <param name="addr"> адрес переменной</param>
        /// <returns> массив байт</returns>
        private byte[] PutToArray(object val, Addr addr)
        {
            try
            {
                switch (addr.ValType)
                {
                    case ValType.ByteT:
                        return new byte[] { Convert.ToByte(val) };

                    case ValType.IntT:
                        return BitConverter.GetBytes(Convert.ToInt16(val));

                    case ValType.DIntT:
                        return BitConverter.GetBytes(Convert.ToInt32(val));

                    case ValType.RealT:
                        return BitConverter.GetBytes(Convert.ToSingle(val));

                    case ValType.StringT:
                        return val.ToString().Select(e => (byte)e).Reverse().ToArray<byte>();

                    default: return null;
                }
            }
            catch { Error = ErrCode.AddrFlt; return new byte[] { }; }
            
        }

      
        #endregion

        #region Внешние методы

        /// <summary>
        /// Иниц-я определенных структур данных
        /// </summary>
        /// <param name="plc"> Описание ПЛК</param>
        /// <returns> ошибка</returns>
        public ErrCode Init(PlcDesc plc)
        {
            _plc = plc;

            // объекты для связи
            _localPnt = new IPEndPoint(IPAddress.Any, 1100);
           
            IPAddress remoteIP;
            if (IPAddress.TryParse(_plc.IP, out remoteIP)) _remotePnt = new IPEndPoint(remoteIP, 102);
            else return ErrCode.ConnFlt;
                       
       
            // Для соединения
            _bSend1OpenConn = new byte[22];
 
            _bSend1OpenConn[0] = 0x03;
            _bSend1OpenConn[1] = 0x00;
            _bSend1OpenConn[2] = 0x00;
            _bSend1OpenConn[3] = 0x16;
            _bSend1OpenConn[4] = 0x11;
            _bSend1OpenConn[5] = 0xE0;
            _bSend1OpenConn[6] = 0x00;
            _bSend1OpenConn[7] = 0x00;
            _bSend1OpenConn[8] = 0x00;
            _bSend1OpenConn[9] = 0x01;  
            _bSend1OpenConn[10] = 0x00;
                                    
            switch (_plc.Cpu)
            {
                case CpuType.S7200:
                    _bSend1OpenConn[11] = 0xC1;
                    _bSend1OpenConn[12] = 0x02;
                    _bSend1OpenConn[13] = 0x10;
                    _bSend1OpenConn[14] = 0x00;
                    _bSend1OpenConn[15] = 0xC2;
                    _bSend1OpenConn[16] = 0x02;
                    _bSend1OpenConn[17] = 0x10;
                    _bSend1OpenConn[18] = 0x00;
                    break;
                case CpuType.S7300:
                case CpuType.S7400:
                case CpuType.S71200:
                    _bSend1OpenConn[11] = 0xC1;
                    _bSend1OpenConn[12] = 0x02;
                    _bSend1OpenConn[13] = 0x01;
                    _bSend1OpenConn[14] = 0x00;
                    _bSend1OpenConn[15] = 0xC2;
                    _bSend1OpenConn[16] = 0x02;
                    _bSend1OpenConn[17] = 0x03;
                    _bSend1OpenConn[18] = (byte)(_plc.Rack * 2 * 16 + _plc.Slot);
                    break;
            }

            _bSend1OpenConn[19] = 0xC0;
            _bSend1OpenConn[20] = 0x01;
            _bSend1OpenConn[21] = 0x09;
           

            _bSend2OpenConn = new byte[25];

            _bSend2OpenConn[0] = 0x03;
            _bSend2OpenConn[1] = 0x00;
            _bSend2OpenConn[2] = 0x00;
            _bSend2OpenConn[3] = 0x19;
            _bSend2OpenConn[4] = 0x02;
            _bSend2OpenConn[5] = 0xF0;
            _bSend2OpenConn[6] = 0x80;
            _bSend2OpenConn[7] = 0x32;
            _bSend2OpenConn[8] = 0x01;
            _bSend2OpenConn[9] = 0x00;
            _bSend2OpenConn[10] = 0x00;
            _bSend2OpenConn[11] = 0xFF;
            _bSend2OpenConn[12] = 0xFF;
            _bSend2OpenConn[13] = 0x00;
            _bSend2OpenConn[14] = 0x08;
            _bSend2OpenConn[15] = 0x00;
            _bSend2OpenConn[16] = 0x00;
            _bSend2OpenConn[17] = 0xF0;
            _bSend2OpenConn[18] = 0x00;
            _bSend2OpenConn[19] = 0x00;
            _bSend2OpenConn[20] = 0x04;  
            _bSend2OpenConn[21] = 0x00;
            _bSend2OpenConn[22] = 0x04;  
            _bSend2OpenConn[23] = 0x03;  
            _bSend2OpenConn[24] = 0xC0;  


            _bSend3OpenConn = new byte[7];

            _bSend3OpenConn[0] = 0x03;
            _bSend3OpenConn[1] = 0x00;
            _bSend3OpenConn[2] = 0x00;
            _bSend3OpenConn[3] = 0x07;
            _bSend3OpenConn[4] = 0x02;
            _bSend3OpenConn[5] = 0xF0;
            _bSend3OpenConn[6] = 0x00;
           

            _bRecOpenConn = new byte[30];


           ////////////////////////// Для чтения///////////////////////////////////
            
            _bSendForRead = new byte[31];

            _bSendForRead[0] = 0x03;
            _bSendForRead[1] = 0x00;
            _bSendForRead[2] = 0x00;
            _bSendForRead[3] = 0x1F;
            _bSendForRead[4] = 0x02;  
            _bSendForRead[5] = 0xF0;
            _bSendForRead[6] = 0x80;
            _bSendForRead[7] = 0x32;
            _bSendForRead[8] = 0x01;
            _bSendForRead[9] = 0x00;
            _bSendForRead[10] = 0x00;
            _bSendForRead[11] = 0x00;
            _bSendForRead[12] = 0x00; 
            _bSendForRead[13] = 0x00;
            _bSendForRead[14] = 0x0E;
            _bSendForRead[15] = 0x00;
            _bSendForRead[16] = 0x00;
            _bSendForRead[17] = 0x04;
            _bSendForRead[18] = 0x01;
            _bSendForRead[19] = 0x12;
            _bSendForRead[20] = 0x0A;
            _bSendForRead[21] = 0x10;
            _bSendForRead[22] = 0x02;
            _bSendForRead[23] = 0x00;  // count
            _bSendForRead[24] = 0x00;  // ..
            _bSendForRead[25] = 0x00;  // db
            _bSendForRead[26] = 0x00;  // ..
            _bSendForRead[27] = 0x84;  // dataType
            _bSendForRead[28] = 0x00;
            _bSendForRead[29] = 0x00;  // startAddr * 8
            _bSendForRead[30] = 0x00;  // ..
           
            
            ////////////////////////// Для записи///////////////////////////////////
            _bSendForWrite = new byte[35];

            _bSendForWrite[0] = 0x03;
            _bSendForWrite[1] = 0x00;
            _bSendForWrite[2] = 0x00;
            _bSendForWrite[3] = 0x27; // packetSize
            _bSendForWrite[4] = 0x02;
            _bSendForWrite[5] = 0xF0;
            _bSendForWrite[6] = 0x80;
            _bSendForWrite[7] = 0x32;
            _bSendForWrite[8] = 0x01;
            _bSendForWrite[9] = 0x00;
            _bSendForWrite[10] = 0x00;
            _bSendForWrite[11] = 0x00; // varCount - 1
            _bSendForWrite[12] = 0x00; // ...
            _bSendForWrite[13] = 0x00; // ...
            _bSendForWrite[14] = 0x0E;
            _bSendForWrite[15] = 0x00; // varCount + 4
            _bSendForWrite[16] = 0x08; // ...
            _bSendForWrite[17] = 0x05;
            _bSendForWrite[18] = 0x01;
            _bSendForWrite[19] = 0x12;
            _bSendForWrite[20] = 0x0A;
            _bSendForWrite[21] = 0x10;
            _bSendForWrite[22] = 0x02;
            _bSendForWrite[23] = 0x00; // count
            _bSendForWrite[24] = 0x04; // ...
            _bSendForWrite[25] = 0x01; // db
            _bSendForWrite[26] = 0xFE; // ...
            _bSendForWrite[27] = 0x84; // dataType
            _bSendForWrite[28] = 0x00;
            _bSendForWrite[29] = 0x00; // startAddr * 8
            _bSendForWrite[30] = 0x20; // ...
            _bSendForWrite[31] = 0x00;
            _bSendForWrite[32] = 0x04;
            _bSendForWrite[33] = 0x00; // count * 8
            _bSendForWrite[34] = 0x20; // ...

            _bRecAfterWrite = new byte[22];
           
            return ErrCode.Ok;
        }

        /// <summary>
        /// Проверка связи
        /// </summary>
        /// <returns> true - ok</returns>
        public bool Ping()
        {
            Ping p = new Ping();
            PingReply pingReplay = p.Send(_plc.IP);
            if (pingReplay.Status == IPStatus.Success) return true;
            else return false;

        }
        
        /// <summary>
        /// Открытие соединения
        /// </summary>
        public void Open()
        {
            if (_isConnOpen) return;
           
            bool isConn = false;
                        
            while (!isConn)
            {
                try
                {
                    _tcpClient = new TcpClient(_localPnt);
                    _tcpClient.Connect(_remotePnt);
                    _netStream = _tcpClient.GetStream();
                    isConn = true;
                }
                catch (SocketException ex)
                {
                    if (ex.ErrorCode == 10048) _localPnt.Port++;
                    else { Error = ErrCode.PingNo; return; }
                }
            }
                      
            _netStream.Write(_bSend1OpenConn, 0, _bSend1OpenConn.Count());
            if (_netStream.Read(_bRecOpenConn, 0, 22) != 22) { Error = ErrCode.ConnFlt; return; }
           
            _netStream.Write(_bSend2OpenConn, 0, _bSend2OpenConn.Count());
            if (_netStream.Read(_bRecOpenConn, 0, 27) != 27) { Error = ErrCode.ConnFlt; return; }
          
            _netStream.Write(_bSend3OpenConn, 0, _bSend3OpenConn.Count());
                       
            _isConnOpen = true;
        }

        /// <summary>
        /// Закрытие соединения
        /// </summary>
        public void Close()
        {
            if (_isConnOpen)
            {
                _tcpClient.Close();
                _isConnOpen = false;
            }
        }

        /// <summary>
        /// Чтение данных DB
        /// </summary>
        public void ReadDataDB()
        {
                                 
            if (!_isConnOpen) return;

            _bSendForRead[27] = 0x84; // dataType  

            foreach (DB db in _DBRead)                               // обходим DB-ки
            {
                byte[] b = BitConverter.GetBytes(db.Num);
                _bSendForRead[25] = b[1];           // db
                _bSendForRead[26] = b[0];           // ..

                IEnumerable<byte> ie = new byte[0];
                              
                for (int bs = 0; bs < db.Bounds.Count; bs += 2)          // обходим области в DB 
                {
                    int leng = db.Bounds[bs + 1] - db.Bounds[bs] + 1;    // длина области - байты, которые надо получить 
                 
                    b = BitConverter.GetBytes(leng);
                    _bSendForRead[23] = b[1];       // count
                    _bSendForRead[24] = b[0];       // ..

                    b = BitConverter.GetBytes(db.Bounds[bs] * 8);
                    _bSendForRead[29] = b[1];       // startAddr * 8
                    _bSendForRead[30] = b[0];       // ..

                    try
                    {    // читаем данные
                        _netStream.Write(_bSendForRead, 0, _bSendForRead.Count());

                        _bRecResultRead = new byte[25 + leng];

                        _netStream.Read(_bRecResultRead, 0, 25 + leng);
                    }
                    catch (System.IO.IOException ex) { Close(); Error = ErrCode.PingNo; return; }
                   
                    // проверка доступа к данным
                    if (_bRecResultRead[21] != 0xff) { Error = ErrCode.DataReadFlt; return; }
                    
                    // соединяем полученные области db
                    byte[] exs;                // вспом массив для заполнения пустой старт области
                    if (bs == 0) exs = new byte[db.Bounds[bs]];
                    else exs = new byte[db.Bounds[bs] - db.Bounds[bs - 1] - 1];
                    
                    ie = ie.Concat(exs.Concat(_bRecResultRead.Skip(25)));
                }
                                      
                // меняем порядок байт
                ie = ie.Reverse();
                byte[] arr = ie.ToArray<byte>();

                for (int hl = 0; hl < db.Handls.Count; hl++)
                {                    
                    // берем результат из массива
                    object res = TakeFromArray(arr, _iReadAddr[db.Handls[hl]]);
                   
                    // текущее значение
                    ItemValue iVal = _iReadRes[db.Handls[hl]];

                    // обновляем, если новое
                    if (!res.Equals(iVal.Value))
                    {
                        iVal.Value = res;
                        ItemsChange.Add(iVal);
                    }   
                } 
            }
                            
        }

        /// <summary>
        /// Запись данных DB
        /// </summary>
        public void WriteDataDB()
        {
            if (!_isConnOpen) return;
          
            _bSendForWrite[27] = 0x84; // dataType  
            
            foreach (DB db in _DBWrite)                                  // обходим DB-ки
            {
                byte[] b = BitConverter.GetBytes(db.Num);
                _bSendForWrite[25] = b[1];           // db
                _bSendForWrite[26] = b[0];           // ..

                for (int bs = 0; bs < db.Bounds.Count; bs += 2)          // обходим области в DB 
                {
                    int leng = db.Bounds[bs + 1] - db.Bounds[bs] + 1;    // длина области - байты, которые надо записать 

                    b = BitConverter.GetBytes(35 + leng);
                    _bSendForWrite[3] = b[0];       // packetSize
                    _bSendForWrite[4] = b[1];

                    b = BitConverter.GetBytes(leng + 4);
                    _bSendForWrite[15] = b[1];      // count + 4
                    _bSendForWrite[16] = b[0];      // ...

                    b = BitConverter.GetBytes(leng);
                    _bSendForWrite[23] = b[1];      // count
                    _bSendForWrite[24] = b[0];      // ...

                    b = BitConverter.GetBytes(db.Bounds[bs] * 8);
                    _bSendForWrite[29] = b[1];      // startAddr * 8
                    _bSendForWrite[30] = b[0];      // ...

                    b = BitConverter.GetBytes(leng * 8);
                    _bSendForWrite[33] = b[1];      // count * 8
                    _bSendForWrite[34] = b[0];      // ...

                    // вспом колл-я - адрес-значение
                    Dictionary<Addr, object> sVal = new Dictionary<Addr, object>(db.Handls.Count);

                    // берем только те адреса, кот входят в тек область                
                    for (int hl = 0; hl < db.Handls.Count; hl++)
                    {
                        Addr adr = _iWriteAddr[db.Handls[hl]];

                        if ((db.Bounds[bs] <= adr.StartByte) && (adr.StartByte <= db.Bounds[bs + 1]))
                            sVal.Add(adr, _iWriteRes[db.Handls[hl]].Value);
                    }
            
                    // сортируем по убыванию старт адреса
                    IOrderedEnumerable<KeyValuePair<Addr, object>> io = sVal.OrderByDescending(e => e.Key.StartByte);

                    // передаем значения в массив байт, раскрывая значения
                    IEnumerable<byte> ie = new byte[0];
                    foreach (KeyValuePair<Addr, object> pair in io)
                                   ie = ie.Concat(PutToArray(pair.Value, pair.Key));
                                       
                    // меняем порядок байт и присоед заголовок
                    ie = _bSendForWrite.Concat(ie.Reverse());
                   
                    // доп проверка
                    if (Error != ErrCode.Ok) continue;
                   
                    try
                    {   // пишем данные
                        _netStream.Write(ie.ToArray<byte>(), 0, ie.Count());
                       
                        // проверка записи
                        _netStream.Read(_bRecAfterWrite, 0, _bRecAfterWrite.Count());
                        if (_bRecAfterWrite[21] != 0xff) { Error = ErrCode.DataWriteFlt; }
                    }
                    catch (System.IO.IOException ex) { Close(); Error = ErrCode.PingNo; }
                    
                }

                // очищаем области в db
                db.Handls.Clear();
                db.Bytes.Clear();
                db.Bounds.Clear();
            }
           
        }

        /// <summary>
        /// Чтение данных M
        /// </summary>
        public void ReadDataM()
        {
           
            if (!_isConnOpen) return;

                _bSendForRead[27] = 0x83; // dataType  
                _bSendForRead[25] = 0x0;  // db num        
                _bSendForRead[26] = 0x0;           

                IEnumerable<byte> ie = new byte[0];
                byte[] b;
                for (int bs = 0; bs < _mRead.Bounds.Count; bs += 2)              // обходим меркер область 
                {
                    int leng = _mRead.Bounds[bs + 1] - _mRead.Bounds[bs] + 1;    // длина области - байты, которые надо получить 

                    b = BitConverter.GetBytes(leng);
                    _bSendForRead[23] = b[1];       // count
                    _bSendForRead[24] = b[0];       // ..

                    b = BitConverter.GetBytes(_mRead.Bounds[bs] * 8);
                    _bSendForRead[29] = b[1];       // startAddr * 8
                    _bSendForRead[30] = b[0];       // ..

                    try
                    {    // читаем данные
                        _netStream.Write(_bSendForRead, 0, _bSendForRead.Count());

                        _bRecResultRead = new byte[25 + leng];

                        _netStream.Read(_bRecResultRead, 0, 25 + leng);
                    }
                    catch (System.IO.IOException ex) { Close(); Error = ErrCode.PingNo; return; }

                    // проверка доступа к данным
                    if (_bRecResultRead[21] != 0xff) { Error = ErrCode.DataReadFlt; return; }

                    // соединяем полученные области 
                    byte[] exs;                // вспом массив для заполнения пустой старт области
                    if (bs == 0) exs = new byte[_mRead.Bounds[bs]];
                    else exs = new byte[_mRead.Bounds[bs] - _mRead.Bounds[bs - 1] - 1];

                    ie = ie.Concat(exs.Concat(_bRecResultRead.Skip(25)));
                }

                // меняем порядок байт
                ie = ie.Reverse();
                byte[] arr = ie.ToArray<byte>();

                for (int hl = 0; hl < _mRead.Handls.Count; hl++)
                {
                    // берем результат из массива
                    object res = TakeFromArray(arr, _iReadAddr[_mRead.Handls[hl]]);

                    // текущее значение
                    ItemValue iVal = _iReadRes[_mRead.Handls[hl]];

                    // обновляем, если новое
                    if (!res.Equals(iVal.Value))
                    {
                        iVal.Value = res;
                        ItemsChange.Add(iVal);
                    }
                }
           

        }

        /// <summary>
        /// Запись данных M
        /// </summary>
        public void WriteDataM()
        {
            if (!_isConnOpen) return;

            _bSendForWrite[27] = 0x83; // dataType  
            _bSendForWrite[25] = 0x0;   // db num  
            _bSendForWrite[26] = 0x0;   

            byte[] b;

            for (int bs = 0; bs < _mWrite.Bounds.Count; bs += 2)          // обходим области 
            {
                int leng = _mWrite.Bounds[bs + 1] - _mWrite.Bounds[bs] + 1;    // длина области - байты, которые надо записать 

                b = BitConverter.GetBytes(35 + leng);
                _bSendForWrite[3] = b[0];       // packetSize
                _bSendForWrite[4] = b[1];

                b = BitConverter.GetBytes(leng + 4);
                _bSendForWrite[15] = b[1];      // count + 4
                _bSendForWrite[16] = b[0];      // ...

                b = BitConverter.GetBytes(leng);
                _bSendForWrite[23] = b[1];      // count
                _bSendForWrite[24] = b[0];      // ...

                b = BitConverter.GetBytes(_mWrite.Bounds[bs] * 8);
                _bSendForWrite[29] = b[1];      // startAddr * 8
                _bSendForWrite[30] = b[0];      // ...
             
                b = BitConverter.GetBytes(leng * 8);
                _bSendForWrite[33] = b[1];      // count * 8
                _bSendForWrite[34] = b[0];      // ...

                // вспом колл-я - адрес-значение
                Dictionary<Addr, object> sVal = new Dictionary<Addr, object>(_mWrite.Handls.Count);

                // берем только те адреса, кот входят в тек область                
                for (int hl = 0; hl < _mWrite.Handls.Count; hl++)
                {
                    Addr adr = _iWriteAddr[_mWrite.Handls[hl]];

                    if ((_mWrite.Bounds[bs] <= adr.StartByte) && (adr.StartByte <= _mWrite.Bounds[bs + 1]))
                        sVal.Add(adr, _iWriteRes[_mWrite.Handls[hl]].Value);
                }

                // сортируем по убыванию старт адреса
                IOrderedEnumerable<KeyValuePair<Addr, object>> io = sVal.OrderByDescending(e => e.Key.StartByte);

                // передаем значения в массив байт, раскрывая значения
                IEnumerable<byte> ie = new byte[0];
                foreach (KeyValuePair<Addr, object> pair in io)
                    ie = ie.Concat(PutToArray(pair.Value, pair.Key));

                // меняем порядок байт и присоед заголовок
                ie = _bSendForWrite.Concat(ie.Reverse());

                // доп проверка
                if (Error != ErrCode.Ok) continue;

                try
                {   // пишем данные
                    _netStream.Write(ie.ToArray<byte>(), 0, ie.Count());
                   
                    // проверка записи
                    _netStream.Read(_bRecAfterWrite, 0, _bRecAfterWrite.Count());
                    if (_bRecAfterWrite[21] != 0xff) { Error = ErrCode.DataWriteFlt; }
                }
                catch (System.IO.IOException ex) { Close(); Error = ErrCode.PingNo; }

            }

            // очищаем области
            _mWrite.Handls.Clear();
            _mWrite.Bytes.Clear();
            _mWrite.Bounds.Clear();
        }
     
        #endregion
              
    }
}
